<?php
/**
 * 系统公共库文件
 * 主要定义系统公共函数库
 */

// =========================================================================================
// @author   sutroon <14507247@qq.com>
// @since 1.0 2014-6-24 by sutroon; 2.0 2014-12-22 by sutroon 重构函数命名,统一加前缀sofn_
// =========================================================================================

/**
 * 将参数转换成数组
 * @param $str 参数,如 a=1&b=2&c=3
 * @param string $col 列分隔符,如 =
 * @param string $row 行分隔符,如 &
 * @return array|false 返回拆分后的数组或false
 * @since 1.0 2014-10-29 by sutroon; 
 * @example 
 *      $arr=sofn_string_params_to_array('a=1&b=2&c=3');
 *      $arr_items = sofn_string_params_to_array($items, '=', ';');
 */
function sofn_string_params_to_array($str, $col = '=', $row = '&') {
    $arr = explode($row, $str);
    $arrOut = false;
    foreach ($arr as $item) {
        $arr2 = explode($col, $item);
        if (empty($arr2[0]) && $arr2[0] !== '0') {
            $arr2[0] = $arr2[1];
        }
        $arrOut[$arr2[0]] = $arr2[1];
    }
    return $arrOut;
}

/**
 * 合并数组为字符串
 * 
 * @param string $glue 合并符号
 * @param array $arr 数组
 * @param int $start 开始索引
 * @param int $end 结束索引
 * @return string
 * @since 1.0 2015-2-7 by sutroon
 * @example sofn_array_implode('|', $item, 4)
 */
function sofn_array_implode($glue, $arr, $start = 0, $end = 0) {
    if ($end > 0) {
        $len = count($arr);
        if ($end >= $len) {
            $end = $len - 1;
        }
    }
    if ($start > 0 && $end > 0 && $start > $end) {
        $start = $end;
    }
    $i = 0;
    $str = '';
    foreach ($arr as $key => $value) {
        if ($start > 0 && $i < $start) {
            $i++;
            continue;
        }
        if ($end > 0 && $i > $end) {
            break;
        }
        $str .= $glue . $value;
        $i++;
    }
    return $str ? substr($str, 1) : $str;
}

/**
 * 返回数组维数
 * @param array $arr
 * @return int mixed
 * @since 1.0 2014-10-21 by http://www.jbxue.com/article/13484.html
 * @example 
 *      sofn_array_level(array(0,1,2)); // 1
 *      sofn_array_level(array(array(0),array(1),array(2))); // 2
 *      sofn_array_level(array(0,array(0,array(1)))); // 3
 */
function sofn_array_level($arr) {
    $al = array(0);
    if (!function_exists('aL')) {

        function aL($arr, &$al, $level = 0) {
            if (is_array($arr)) {
                $level++;
                $al[] = $level;
                foreach ($arr as $v) {
                    aL($v, $al, $level);
                }
            }
        }

    }
    aL($arr, $al);
    return max($al);
}

/**
 * 遍历数组并检查数组中的元素是否包含在内容中
 * @param string $content
 * @param array $key_array
 * @param bool $key_is_content 是否把数组元素当内容(在数组元素中搜索内容),默认false
 * @return bool
 * @since 1.0 2014-12-22 by sutroon
 * @example $sop=sofn_array_key_contains(array('soc','zhan123'));
 */
function sofn_array_key_contains($content, $key_array, $key_is_content = false) {
    if (!is_array($key_array)) {
        return false;
    }
    if ($key_is_content) {
        foreach ($key_array as $k) {
            if (false === strpos($k, $content)) {
                continue;
            }
            return true;
        }
    } else {
        foreach ($key_array as $k) {
            if (false === strpos($content, $k)) {
                continue;
            }
            return true;
        }
    }
    return false;
}

/**
 * 判断是否为索引数组(没有键名)
 * @param array $array
 * @return bool
 * @since 1.0 2014-6-24 by sutroon
 * @example 
 *      sofn_is_assoc_array(array(0,1,2)) // false
 *      sofn_is_assoc_array(array('a'=>1,'b'=>2)) // true
 */
function sofn_is_assoc_array($array) {
    return array_keys($array) !== range(0, count($array) - 1);
}

/**
 * 检测字符串是否已指定的字符串开头
 * @param string $str
 * @param string $search
 * @return bool
 * @since 1.0.0 2013-10-7 sutroon
 * @example sofn_start_with('this is my words','th');
 */
function sofn_start_with($str, $search) {
    if (strlen($str) < strlen($search)) { //若第一个字符串长度小于第二个的时候，必须指定返回false，
        return false; //否则substr_compare遇到这种情况会返回0（即相当，与事实不符合）
    } else {
        return !substr_compare($str, $search, 0, strlen($search)); //当第一个长度大于第二个时才开始判断
    }
}

/**
 * 检测字符串是否已指定的字符串结尾
 * @param string $str 要查找的源字符串
 * @param string $search 关键词
 * @param bool $case_insensitivity 是否忽略大小写区别(默认不忽略)
 * @return bool
 * @example sofn_end_with('my words','ds');
 * @since
 *      1.0.1 2014-9-1 by sutroon 新增$case_insensitivity参数
 *      1.0.0 2013-10-7 by sutroon 创建
 */
function sofn_end_with($str, $search, $case_insensitivity = false) {
    if (strlen($str) < strlen($search)) { //若第一个字符串长度小于第二个的时候，必须指定返回false，
        return false; //否则substr_compare遇到这种情况会返回0（即相当，与事实不符合）
    } else {
        return !substr_compare($str, $search, strlen($str) - strlen($search), strlen($search), $case_insensitivity); //从第一个字符串减去第二个字符串长度处开始判断
    }
}

/**
 * 过滤前缀
 * @param string $str
 * @param string $trim
 * @return string
 * @since 1.0 2014-8-25 by sutroon
 * @example trim_start('00.100.00','00.')
 */
function sofn_trim_start($str, $trim) {
    $lentrim = strlen($trim);
    if (strlen($str) < $lentrim) {
        return $str;
    }
    if (substr($str, 0, $lentrim) == $trim) {
        return substr($str, $lentrim);
    }
    return $str;
}

/**
 * 过滤后缀
 * @param string $str
 * @param string $trim
 * @return string
 * @since 1.0 2014-8-25 by sutroon
 * @example trim_end('00.100.00','.00')
 */
function sofn_trim_end($str, $trim) {
    $lentrim = strlen($trim);
    $lenstr = strlen($str);
    if ($lenstr < $lentrim) {
        return $str;
    }
    if (substr($str, $lenstr - $lentrim) == $trim) {
        return substr($str, 0, $lenstr - $lentrim);
    }
    return $str;
}

/**
 * 将秒钟数格式化为时分秒的时间
 * @param int $seconds 秒钟数
 * @return string 时分秒时间格式
 * @since 1.0 2014-9-12 by sutroon
 * @example sofn_seconds_format(61) // 00:01:01
 */
function sofn_seconds_format($seconds) {
    if (!$seconds) {
        return '0:0:0';
    }
    if (!is_numeric($seconds)) {
        return $seconds;
    }
    $h = intval($seconds / 3600);
    $m = intval(($seconds % 3600) / 60);
    $s = $seconds % 60;
    return "$h:$m:$s";
}

/**
 * 格式化时间
 * @param string $time 字符串时间
 * @param string $fmt 时间格式
 * @return string
 * @since 1.0 2015-3-6 by sutroon
 * @example sofn_time_format('2015-5-4 13:21:30','H:i');
 */
function sofn_time_format($time, $fmt) {
    return ($time && $fmt) ? date($fmt, strtotime($time)) : $time;
}

/**
 * 时间轴开发，即显示为“刚刚”、“5分钟前”、“昨天10:23”等
 * @param string $time
 * @return string
 * @since 1.0.0 2013-5-4 sutroon
 * @example sofn_tran_time('2013-5-4 13:23:33');
 */
function sofn_tran_time($time) {
    if (!is_numeric($time)) {
        $time = strtotime($time);
    }
    $rtime = date("m-d H:i", $time);
    $htime = date("H:i", $time);
    $time = time() - $time;
    if ($time < 60) {
        $str = '刚刚';
    } elseif ($time < 60 * 60) {
        $min = floor($time / 60);
        $str = $min . '分钟前';
    } elseif ($time < 60 * 60 * 24) {
        $h = floor($time / (60 * 60));
        $str = $h . '小时前 ' . $htime;
    } elseif ($time < 60 * 60 * 24 * 3) {
        $d = floor($time / (60 * 60 * 24));
        if ($d == 1) {
            $str = '昨天 ' . $rtime;
        } else {
            $str = '前天 ' . $rtime;
        }
    } else {
        $str = $rtime;
    }
    return $str;
}

/**
 * 计算时间差
 * @param string $time1 时间1
 * @param string $time2 时间2
 * @param string $format 格式
 * @return string
 * @since 1.0 <2015-4-24> SoChishun Added.
 * @example sofn_date_diff('2015-4-24 10:20:30','2015-4-24 17:42:33','h:i');
 */
function sofn_date_diff($time1, $time2, $format = 'd天h时i分') {
    $diff = strtotime($time2) - strtotime($time1);
    $date['d'] = floor($diff / 86400);
    $timepart = $diff % 86400;
    $date['h'] = floor($timepart / 3600);
    $date['H'] = $date['h'] < 10 ? '0' . $date['h'] : $date['h'];
    $date['i'] = floor($timepart % 3600 / 60);
    $date['I'] = $date['i'] < 10 ? '0' . $date['i'] : $date['i'];
    $date['s'] = floor($timepart % 60);
    $date['S'] = $date['s'] < 10 ? '0' . $date['s'] : $date['s'];
    foreach ($date as $key => $value) {
        $format = str_replace($key, $value, $format);
    }
    return $format;
}

/**
 * 强制弹出保存文件对话框
 * @param string $default_file_name 对话框中默认文件名
 * @param string $content 要保存的内容
 * @param bool $is_path 指示$content参数是内容还是文件路径,默认false
 * @since 1.0 2014-10-11 by sutroon; 1.1 2015-1-5 by sutroon 新增路径支持功能
 * @example sofn_save_file_dialog('image1.jpg',file_get_contents($path));
 */
function sofn_save_file_dialog($default_file_name, $content, $is_path = false) {
    if ($is_path) {
        if (file_exists($content)) {
//打开文件  
            $file = fopen($content, "r");
//输入文件标签   
            Header("Content-type: application/octet-stream");
            Header("Accept-Ranges: bytes");
            Header("Accept-Length: " . filesize($content));
            Header("Content-Disposition: attachment; filename=" . $default_file_name);
//读取文件内容并直接输出到浏览器  
            echo fread($file, filesize($content));
            fclose($file);
            exit();
        } else {
            die('抱歉, 服务器上文件已删除或更名, 请联系管理员!');
        }
    } else {
        if (false === $content) {
            die('抱歉, 服务器上文件已删除或更名, 请联系管理员!');
        }
        header("Content-Type: application/force-download");
        header('Content-Disposition: attachment; filename="' . $default_file_name . '"');
        echo $content;
        exit(0);
    }
}

/**
 * 导出Excel
 * @param array $list 数据集
 * @param array $options 更多选项,包含 string:FILENAME, string:SHEETNAME, int:ActiveSheet
 * @since 1.0 2014-9-5 by sutroon
 * @since 1.1 <2015-4-10> SoChishun 修正导出大数值自动变为科学计数的问题
 * @example sofn_excel_export($list);
 */
function sofn_excel_export($list, $options = array()) {
    $options = array_merge(array('FILENAME' => 'EXCEL' . date('Ymd-His'), 'SHEETNAME' => 'Sheet1', 'ActiveSheet' => 0, 'SaveTemp' => false), is_array($options) ? $options : array());
    vendor('PHPExcel.PHPExcel');
    $objPHPExcel = new \PHPExcel();
    $objPHPExcel->getProperties()->setCreator("XCall")
            ->setLastModifiedBy("PHPExcel")
            ->setTitle("Office 2007 XLSX Test Document")
            ->setSubject("Office 2007 XLSX Test Document")
            ->setDescription("Test document for Office 2007 XLSX, generated using PHP classes.")
            ->setKeywords("office 2007 openxml php")
            ->setCategory("Test result file");
    $sheet = $objPHPExcel->getActiveSheet($options['ActiveSheet']);
    $i = 1;
    foreach ($list as $row) {
        if ($i == 1) {
            $c = 0;
            foreach ($row as $k => $v) {
                $sheet->setCellValueByColumnAndRow($c, $i, $k);
                $c++;
            }
        }
        $i++;
        $c = 0;
        foreach ($row as $k => $v) {
            //$sheet->setCellValueByColumnAndRow($c, $i, $v);
            $sheet->setCellValueExplicitByColumnAndRow($c, $i, $v, \PHPExcel_Cell_DataType::TYPE_STRING);
            $c++;
        }
    }
    $objPHPExcel->getActiveSheet()->getHeaderFooter()->setOddHeader('&L&G&C&HPlease treat this document as confidential!');
    $objPHPExcel->getActiveSheet()->getHeaderFooter()->setOddFooter('&L&B' . $objPHPExcel->getProperties()->getTitle() . '&RPage &P of &N');
    $objPHPExcel->getActiveSheet()->setTitle($options['SHEETNAME']);
    $filename = $options['FILENAME'] . '.xlsx';
    //$filename=  iconv('UTF-8', 'ISO_8859_1', $filename);
    $objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
    //header('Content-Type: application/vnd.ms-excel');
    header('Content-Type: application/octet-stream');
    header("Content-Disposition: attachment;filename=$filename");
    header('Cache-Control: max-age=0');
    $objWriter->save('php://output');
    exit(0);
}

/**
 * 将Excel文件转为数组
 * @param string $path Excel文件的服务器路径
 * @param array $options 更多选项,包含 int:START, int:COUNT, int:ActiveSheet
 * @return mixed 如果文件存在则返回array,否则返回false,数组键是标题名称
 * @since 1.0 2014-9-5 by sutroon
 * @since 1.1 <2015-4-29> SoChishun 新增ONLY_GET_COUNT参数用于统计
 * @example $arr_content = sofn_excel_import(UPLOAD_ROOT . $msg['filePath']);
 */
function sofn_excel_import($path, $options = array()) {
    $options = array_merge(array(
        'START' => 2, // 起始数据行,默认2(第一行为标题)
        'COUNT' => 0, // 数量,0=不限
        'ActiveSheet' => 0, // 页面索引,默认0
        'ONLY_GET_COUNT' => false, // 是否只返回数量而不是数据数组(用于数量判断)
            ), is_array($options) ? $options : array());
    if (!file_exists($path)) {
        return false;
    }
    vendor('PHPExcel.PHPExcel');
    $objPHPExcel = \PHPExcel_IOFactory::load($path);
    $sheet = $objPHPExcel->getActiveSheet($options['ActiveSheet']);
    $highestRow = $sheet->getHighestRow(); // 取得总行数,数值,如:30
    $highestColumnName = $sheet->getHighestColumn(); // 取得总列数,字符,如:F
    $highestColumn = \PHPExcel_Cell::columnIndexFromString($highestColumnName); // 转换成数值
    if ($options['ONLY_GET_COUNT']) { // 如果只是返回数量统计,则到此退出,提高效率
        return array('rows' => $highestRow, 'cols' => $highestColumn);
    }
    $arr = array();
    $i = $options['START'];
    $count = $options['COUNT'];
    while ($i <= $highestRow) {
        if ($count > 0 && ($i - 2) > $count) {
            break;
        }
        for ($c = 0; $c < $highestColumn; $c++) {
            $cell = $sheet->getCellByColumnAndRow($c, $i);
            $value = $cell->getValue();
            if ($value instanceof \PHPExcel_RichText) {
//富文本转换字符串  
                $value = $value->__toString();
            }
            if ($cell->getDataType() == \PHPExcel_Cell_DataType::TYPE_NUMERIC) {
// 格式化日期
                $cellstyleformat = $cell->getStyle()->getNumberFormat();
                $formatcode = $cellstyleformat->getFormatCode();
                if (preg_match('/^(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy]/i', $formatcode)) {
                    $value = gmdate("Y-m-d", \PHPExcel_Shared_Date::ExcelToPHP($value));
                } else {
                    $value = \PHPExcel_Style_NumberFormat::toFormattedString($value, $formatcode);
                }
            }
            $key = $sheet->getCellByColumnAndRow($c, 1)->getValue();
            if ($key instanceof \PHPExcel_RichText) {
//富文本转换字符串  
                $key = $key->__toString();
            }
            $arr[$i - 2][$key] = $value;
        }
        $i++;
    }
    return $arr;
}

/**
 * 更新iptable
 * <br />如：update_iptables(get_client_ip());
 * <br />update_iptables('udp:5060','upd:5666');
 * @param string|int $value
 * @param string|int $oldvalue
 * @param string $filename
 * @param bool $restart
 * @return string|true 成功返回true，否则返回错误字符串
 * @since 1.0 2015-1-27 by sutroon
 */
function sofn_update_iptables($value, $oldvalue = 0, $filename = '/etc/sysconfig/iptables', $restart = FALSE) {
    if (!is_file($filename)) {
        return 'E_FILE_NO_FOUND';
    }
    $data = file_get_contents($filename);
    $keyword = '-A INPUT -j DROP';
    $pos = strpos($data, $keyword);
    if (false === $pos) {
        $keyword = 'COMMIT';
        $pos = strpos($data, $keyword);
        if (false === $pos) {
            return 'E_FILE_FORMAT';
        }
    }
    $iprule_old = false;
    if (false === strpos($value, '.')) {
        if ($oldvalue) {
            $arrValue = false === strpos($oldvalue, ':') ? array('tcp', $oldvalue) : explode(':', $oldvalue);
            $iprule_old = '-A INPUT -p ' . $arrValue[0] . ' -m udp --dport ' . $arrValue[1] . ' -j ACCEPT';
        }
        $arrValue = false === strpos($value, ':') ? array('tcp', $value) : explode(':', $value);
        $iprule = '-A INPUT -p ' . $arrValue[0] . ' -m udp --dport ' . $arrValue[1] . ' -j ACCEPT';
    } else {
        $iprule = "-A INPUT -s $value/255.255.255.255 -j ACCEPT";
    }
    if ($iprule_old) {
        if (false === strpos($data, $iprule_old)) {
            if (false === strpos($data, $iprule)) {
                $data = substr($data, 0, $pos) . $iprule . PHP_EOL . substr($data, $pos);
            }
        } else {
            $data = str_replace($iprule_old, $iprule, $data);
        }
    } else {
        if (false === strpos($data, $iprule)) {
            $data = substr($data, 0, $pos) . $iprule . PHP_EOL . substr($data, $pos);
        } else {
            return 'E_EXISTS';
        }
    }
    file_put_contents($filename, $data);
    if ($restart) {
        sleep(1);
        $msg = exec("sudo /sbin/service iptables restart", $a, $i);
        if ($i) {
            return false; // 出错
        }
        /*
          if (false === system("sudo /sbin/service iptables restart", $code)) {
          return $code;
          } */
    }
    return true;
}

/**
 * 获取目录最后节点名称
 * @param string $path 路径名称
 * @return string
 * @since 1.0 2014-6-24 by sutroon
 * @example $lastdir = sofn_get_last_dir_name(dirname($path));
 */
function sofn_get_last_dir_name($path) {
    return substr($path, strrpos($path, '/') + 1);
}

/**
 * 获取文件后缀名(不带点号)
 * @param string $path
 * @return string
 * @since 1.0 2014-6-24 by sutroon
 * @example $ext = sofn_get_file_ext($path);
 */
function sofn_get_file_ext($path) {
    if (false !== ($pos = strrpos($path, '.'))) {
        return substr($path, $pos + 1);
    }
    return '';
}

/**
 * 更改文件后缀
 * @param $path 文件名称
 * @param $ext 后缀名称
 * @return string 更改后的新文件名
 * @since 1.0 2014-9-23 by sutroon
 * @example sofn_change_file_ext($path,'wav');
 */
function sofn_change_file_ext($path, $ext) {
    if (!$path || strpos($path, '.') === false) {
        return $path;
    }
    return substr($path, 0, strrpos($path, '.') + 1) . $ext;
}

/**
 * 循环删除目录和文件函数  
 * @param string $dirName 目录路径
 * @return false|array 返回不能删除的目录名称
 * @since 1.0 2015-1-20 http://blog.csdn.net/china_skag/article/details/7423559
 */
function sofn_del_dirAndFile($dirName) {
    $unlinks = false;
    if ($handle = opendir("$dirName")) {
        while (false !== ( $item = readdir($handle) )) {
            if ($item != "." && $item != "..") {
                if (is_dir("$dirName/$item")) {
                    $unlinks = $unlinks + sofn_del_dirAndFile("$dirName/$item");
                } else {
                    if (!unlink("$dirName/$item"))
                        $unlinks[] = $item;
                }
            }
        }
        closedir($handle);
        if (!rmdir($dirName)) {
            $unlinks[] = $dirName;
        }
    }
    return $unlinks;
}

/**
 * 删除目录下的文件
 * @param string $dirName 目录路径
 * @return false|array 返回不能删除的目录名称
 * @since 1.0 2015-1-20 http://blog.csdn.net/china_skag/article/details/7423559
 */
function sofn_del_fileUnderDir($dirName) {
    $unlinks = false;
    if ($handle = opendir("$dirName")) {
        while (false !== ( $item = readdir($handle) )) {
            if ($item != "." && $item != "..") {
                if (is_dir("$dirName/$item")) {
                    $unlinks = $unlinks + sofn_del_fileUnderDir("$dirName/$item");
                } else {
                    if (!unlink("$dirName/$item"))
                        $unlinks[] = $item;
                }
            }
        }
        closedir($handle);
    }
    return $unlinks;
}

/**
 * 过滤上传文件中的无效文件
 * @param array|false $emptys 无效文件名数组
 * @param array $files 文件,一般是$_FILES
 * @return array 返回过滤后的有效文件数组
 * @since 1.0 <2015-3-27> SoChishun Added.
 */
function sofn_file_filter(&$emptys, $files = '') {
    if ('' === $files) {
        $files = $_FILES;
    }
    $emptys = false;
    foreach ($files as $name => $file) {
        if ($file['error'] > 0) {
            $emptys[] = $name;
        }
    }
    if ($emptys) {
        foreach ($emptys as $name) {
            unset($files[$name]);
        }
    }
    return $files;
}

/**
 * 上传文件
 * <br />注意：请确保目录存在(不会自动创建目录)
 * @param array $options 选项 
 * <br /> 'rootPath' => './Uploads/', // 保存根路径
 * <br />'savePath' => '', // 保存次目录,如 Images/或Faces/
 * <br />'autoSub' => true, // 自动子目录保存文件
 * <br />'subName' => array('date', 'Y-m-d'), // 子目录创建方式，[0]-函数名，[1]-参数，多个参数使用数组
 * <br />'saveName' => array('uniqid', ''), // 上传文件命名规则，[0]-函数名，[1]-参数，多个参数使用数组, 空数组表示保持原名,字符串表示新文件名(无后缀)
 * <br />'saveExt' => '', // 文件保存后缀，空则使用原后缀
 * <br />'replace' => true, // 存在同名是否覆盖,false则提示存在同名文件
 * <br /> 'maxSize' => 2097152, // 上传的文件大小限制 (0-不做限制,2097152=2MB,512000=500KB,204800=200KB)
 * <br />'exts' => array('gif', 'jpg', 'jpeg', 'bmp', 'png'),  //允许上传的文件后缀
 * <br />'skipEmpty' => true // 是否过滤空文件,防止表单无文本域出错
 * @param array $files 文件,如果为''则默认为$_FILES
 * @return string|array 失败则返回错误字符串,成功则返回二维数组:array('文件域名称'=>array('filepath'=>'文件保存的具体路径','savename'=>'文件名','size'=>'文件大小','type'=>'文件MiMe类型','ext'=>'文件后缀'))
 * @since 1.0 <2015-3-27> SoChishun Added.
 * @since 1.1 <2015-7-16> SoChishun 完善注释.
 * @example 取值filepath,savename
 *      上传到子目录：
  $msg = sofn_upload(array('savePath' => 'Import/', 'exts' => array('xlsx', 'xls', 'txt'), 'autoSub' => true, 'subType' => 'date', 'saveName' => array(), 'replace' => TRUE));
  if (!is_array($msg)) {
  return $this->returnMsg(false, $msg);
  }
 *      上传商品
 *      $files = sofn_upload(array('skipEmpty' => true,'savePath'=>'Product/'));
  if (is_array($files)) {
  $this->thumb_url = $files['thumb_url']['filepath'];
  }
 *      $msg=sofn_upload(array('savePath'=>'Product/','maxSize'=>512000,'skipEmpty'=>true));
 */
function sofn_upload($options = array(), $files = '') {
    // 过滤空文件,防止出错
    if (isset($options['skipEmpty']) && $options['skipEmpty']) {
        if ($options['skipEmpty']) {
            $files = sofn_file_filter($emptys, $files);
            if (!$files) {
                return '没有文件被上传!';
            }
        }
        unset($options['skipEmpty']);
    }
    $config = array(
        'rootPath' => './Uploads/', // 保存根路径
        'savePath' => '', // 保存次目录,如 Images/或Faces/
        'autoSub' => true, // 自动子目录保存文件
        'subName' => array('date', 'Y-m-d'), // 子目录创建方式，[0]函数名，[1]-参数，多个参数使用数组
        'saveName' => array('uniqid', ''), // 上传文件命名规则，[0]函数名，[1]-参数，多个参数使用数组, 空数组表示保持原名,字符串表示新文件名(无后缀)
        'saveExt' => '', // 文件保存后缀，空则使用原后缀
        'replace' => true, // 存在同名是否覆盖,false则提示存在同名文件
        'maxSize' => 0, // 上传的文件大小限制 (0-不做限制,2097152=2MB,512000=500KB,204800=200KB)
        'exts' => array('g729', 'mp3', 'wav', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'txt', 'zip', 'rar', 'doc', 'docx', 'xls', 'xlsx'), //允许上传的文件后缀
        'hash' => false, // 是否生成hash编码
        'mimes' => array(), // 允许上传的文件MiMe类型
    );
    $config = array_merge($config, $options);
    is_dir($config['rootPath']) || mkdir($config['rootPath']); // 自动创建上传根目录
    $uploader = new \Think\Upload($config);
    $infos = $uploader->upload($files);
    if ($uploader->getError()) {
        return $uploader->getError();
    }
// 新增一个filepath合成路径
    $returns = array();
    foreach ($infos as $key => $file) {
        $file['filepath'] = ltrim($config['rootPath'], '.') . $file['savepath'] . $file['savename'];
        $returns[$key] = $file;
    }
    return $returns;
}

/**
 * 上传文件(支持一个或多个文件上传),并保存到数据库
 * <br />注意：请确保目录存在(不会自动创建目录)
 * @param array $options 上传配置
 * <br /> 'rootPath' => './Uploads/', // 保存根路径
 * <br />'savePath' => '', // 保存次目录,如 Images/或Faces/
 * <br />'autoSub' => true, // 自动子目录保存文件
 * <br />'subName' => array('date', 'Ymd'), // 子目录创建方式，[0]-函数名，[1]-参数，多个参数使用数组
 * <br />'saveName' => array('uniqid', ''), // 上传文件命名规则，[0]-函数名，[1]-参数，多个参数使用数组, 空数组表示保持原名,字符串表示新文件名(无后缀)
 * <br />'saveExt' => '', // 文件保存后缀，空则使用原后缀
 * <br />'replace' => true, // 存在同名是否覆盖,false则提示存在同名文件
 * <br /> 'maxSize' => 2097152, // 上传的文件大小限制 (0-不做限制,2097152=2MB,512000=500KB,204800=200KB)
 * <br />'exts' => array('gif', 'jpg', 'jpeg', 'bmp', 'png'),  //允许上传的文件后缀
 * <br />'skipEmpty' => true // 是否过滤空文件,防止表单无文本域出错
 * @param boolean|array $exdb 是否保存到数据库(如为数组则表示附加到数据库的字段值)
 * @return string|array 失败则返回错误字符串,成功则返回二维数组:array('文件域名称'=>array(fileName,filePath,fileExt,fileSize,fileID))
 * @example 取值：fileName, filePath
 *      上传到子目录
 *      $msg = sofn_upload_handle(array('savePath' => $category . '/', 'maxSize' => 512000, 'skipEmpty' => true), $exdb);
  if (is_array($msg)) {        }
 *      上传到其他绝对目录,禁止自动生成子目录,并指定文件名：
 *      sofn_upload_handle(array('rootPath' => '/var/lib/asterisk/moh/xcall_1_nu2/', 'autoSub' => false, 'exts' => $allowExts, 'saveName' => 'orig_' . $fnotext), array('dirName' => '', 'exTags' => 'moh', 'fileName' => 'wav_liantong.wav', 'filePath' => '/var/lib/asterisk/moh/xcall_1_nu2/wav_liantong.wav'));
 *      上传到其他绝对目录,禁止自动生成子目录
 *      $msg = sofn_upload_handle(array('rootPath'=>$_SERVER['DOCUMENT_ROOT'] . '/','savePath' => 'xcallsqzs/Licence/', 'saveName' => 'licence','autoSub'=>false));
 * @since 1.0 2014-6-30 by sutroon
 * @since 1.1 2014-7-18 by sutroon 新增saveRule，将其他参数合并成$exData
 * @since 1.2 2014-8-30 by sutroon 将saveRule和$exData参数合并到$config参数中,并新增若干可定制上传参数,支持缩略图生成,支持自动生成子目录 (搞定了上传这个心病,几天来压抑的心情舒坦多了^_^)
 * @since 1.3 2014-10-21 by sutroon 修正有savePath子路径时无法自动创建完整路径的问题
 * @since 2.0 <2015-7-16> SoChishun 适配TP3.2和sofn_upload()方法进行重构,参数更合理,统一返回二维数组.
 */
function sofn_upload_handle($options = array(), $exdb = array()) {
    $files = sofn_upload($options);
    if (!is_array($files)) {
        return $files;
    }
    $datas = array();
    foreach ($files as $name => $info) {
        $data = null;
        if (!is_array($exdb) || !isset($exdb['fileName'])) {
            $data['fileName'] = $info['savename']; // wangluoqingge.mp3
        }
        if (!is_array($exdb) || !isset($exdb['filePath'])) {
            $data['filePath'] = $info['filepath']; // /var/lib/asterisk/moh/xcall_13_002/wangluoqingge.mp3
        }

        $data['fileSize'] = $info['size']; // 877769
        $data['fileType'] = $info['type']; // audio/mp3
        $data['fileExt'] = $info['ext']; // mp3
        if (false !== $exdb) {
            $table = 'db_xcall.tgeneral_file';
            if (isset($exdb['TABLE'])) {
                $table = $exdb['TABLE'];
                unset($exdb['TABLE']);
            }
            if (is_array($exdb)) {
                $data = array_merge($data, $exdb);
            }
            $where['filePath'] = $data['filePath'];
            $M = M($table);
            $result = $M->where($where)->getField('id');
            if (!$result) {
                $result = $M->add($data);
                if (false === $result) {
                    return '数据库写入失败!' . $M->getDbError();
                }
            }
            $data['fileID'] = $result;
        }
        $datas[$name] = $data;
    }
    return $datas;
}

/**
 * 非空字符串
 * 用默认值替代空字符串
 * @param string $val
 * @param mixed $defv
 * @return mixed
 * @since 1.0 2014-11-7 by sutroon
 * @example sofn_not_empty('','Default');
 */
function sofn_not_empty($val, $defv) {
    return (strlen($val) < 1) ? $defv : $val;
}

/**
 * 通用获取状态名称方法
 * @param string $v 值
 * @param string $type 类型
 *      yesno=否|是,activate=未激活|已激活,approval=未审核|已审核,startstop=已停止|已启动|已暂停,enable=已禁用|已启用,login:登出|登入,user=正常|待审核|禁用,sex=女士|先生|保密,marital=未婚|已婚|保密
 * @return string
 * @since 1.0 2014-7-7 by sutroon, 2014-12-3 新增$undefined
 * @example {$vo.state|sofn_get_state_text=user,###}
 */
function sofn_get_state_text($type, $v, $undefined = null) {
    $set = false;
    switch (strtolower($type)) {
        case 'user-state':
            $set = array('0' => '待审核', '1' => '正常', '3' => '冻结', '4' => '禁用', '7' => '注销', '14' => '回收站');
            break;
        case 'online-state':
            $set = array('0' => '离线', '1' => '在线', '2' => '隐身', '3' => '离开', '5' => '忙碌');
            break;
        case 'start-stop':
            $set = array('0' => '已停止', '1' => '已启动', '2' => '已暂停');
            break;
        case 'activate-state':
            $set = array('0' => '未激活', '1' => '已激活');
            break;
        case 'yes-no':
            $set = array('0' => '否', '1' => '是');
            break;
        case 'success-fail':
            $set = array('0' => '失败', '1' => '成功');
            break;
        case 'display-hide':
            $set = array('0' => '隐藏', '1' => '显示');
            break;
        case 'approval-state':
            $set = array('0' => '未审核', '1' => '已审核', '4' => '已关闭');
            break;
        case 'enable':
            $set = array('0' => '已禁用', '1' => '已启用');
            break;
        case 'login':
            $set = array('0' => '已登出', '1' => '已登入');
            break;
        case 'sex':
            $set = array('0' => '女士', '1' => '先生', '2' => '保密');
            break;
        case 'marital':
            $set = array('0' => '未婚', '1' => '已婚', '2' => '保密');
            break;
        case 'cti-call-result':
            $set = array('ANSWERED' => '应答', 'NO ANSWER' => '无应答', 'CONGESTION' => '堵塞', 'BUSY' => '忙音', 'FAILED' => '失败');
            break;
        case 'cti-channel-state':
            $set = array('0' => '空闲', '1' => '正在呼叫', '2' => '通话中', '3' => '呼叫成功', '4' => '呼叫失败');
            break;
        case 'cti-call-forwarding-type':
            $set = array('0' => '', '1' => '无条件转接', '2' => '忙时转移', '3' => '无应答或不可用时转移');
            break;
        case 'message-send-state':
            $set = array('0' => '未发送', '1' => '发送成功', '2' => '发送失败');
            break;
        case 'record-source':
            $set = array('import' => '导入', 'add' => '添加', 'generate' => '生成');
            break;
        case 'pay-type':
            $set = array('0' => '后付费', '1' => '预付费');
            break;
        case 'settlement-status':
            $set = array('0' => '未结算', '1' => '已结清', '2' => '部分结算');
            break;
        case 'confirm':
            $set = array('0' => '未审核', '1' => '审核通过', '4' => '审核拒绝');
            break;
        case 'input-type':
            $set = array('text' => '单行文本框', 'date' => '日期', 'datetime' => '日期和时间', 'time' => '时间', 'select' => '下拉选择框', 'radio' => '单选框', 'checkbox' => '复选框', 'textarea' => '多行文本框', 'editor' => '可视化编辑器', 'file' => '文件上传框');
            break;
        case 'product-sale-state': // 商品销售状态
            $set = array('0' => '下架', '1' => '上架');
            break;
        case 'product-operate-type': // 商品进销存操作类型 2014-12-23 by sutroon
            $set = array('SELL-RETURN' => '销售退货(入库)', 'SALE' => '销售出库', 'PURCHASE' => '采购入库', 'INTERNAL' => '内部出库', 'INVENTORY' => '盘点调仓');
            break;
        case 'customer-appointment-state': // 客户预约状态 2014-12-24 by sutroon
            $set = array('0' => '未回访', '1' => '回访成功', '2' => '回访失败', '4' => '放弃回访');
            break;
        case 'customer-appointment-source': // 客户预约来源 2014-12-24 by sutroon
            $set = array('USER' => '自定义', 'ORDER' => '订单回访');
            break;
        case 'customer-buy-interested': // 客户购买意向 2014-12-25 by sutroon
            $set = array('0' => '普通', '1' => '意向购买', '2' => '非常兴趣');
            break;
    }
    $str = false;
    if ($set) {
        if (array_key_exists($v, $set)) {
            $str = $set[$v];
        } else if (array_key_exists('*', $set)) {
            $str = $set['*'];
        } else {
            $str = '';
        }
    }
    if (false === $str) {
        $str = is_null($undefined) ? "[$type|$v]" : $undefined;
    }
    return $str;
}

/**
 * 获取存储过程标准返回值描述
 * @param int $code 返回代码
 * @param array $arr_msg 覆盖的描述
 * @return string 代码对应的描述
 * @since 1.0 2014-10-16 by sutroon
 * @example sofn_get_procedure_result_text($result['@oint_result'],array('1'=>'注册成功,请重新登录','4300'=>'手机号码已存在'));
 */
function sofn_get_procedure_result_text($code, $arr_msg = array()) {
    $arr_msg = $arr_msg + array(1 => '执行成功', 4000 => '系统错误', 4100 => '参数错误', 4200 => '状态有误', 4300 => '记录已存在', 4400 => '记录不存在', 4500 => '值不在范围内(小于下限或大于上限)');
    return array_key_exists($code, $arr_msg) ? $arr_msg[$code] : '未知错误' . $code;
}

/**
 * 判断是否手机端访问
 * @staticvar string $mobilebrowser_list
 * @return bool
 * @since 1.0 2014-12-9 http://www.thinkphp.cn/topic/8081.html
 */
function isMobile() {
    $mobile = array();
    static $mobilebrowser_list = 'Mobile|iPhone|Android|WAP|NetFront|JAVA|OperasMini|UCWEB|WindowssCE|Symbian|Series|webOS|SonyEricsson|Sony|BlackBerry|Cellphone|dopod|Nokia|samsung|PalmSource|Xphone|Xda|Smartphone|PIEPlus|MEIZU|MIDP|CLDC';
//note 获取手机浏览器
    if (preg_match("/$mobilebrowser_list/i", $_SERVER['HTTP_USER_AGENT'], $mobile)) {
        return true;
    } else {
        if (preg_match('/(mozilla|chrome|safari|opera|m3gate|winwap|openwave)/i', $_SERVER['HTTP_USER_AGENT'])) {
            return false;
        } else {
            if ($_GET['mobile'] === 'yes') {
                return true;
            } else {
                return false;
            }
        }
    }
}

/**
 * 设置图片
 * @param string $url 图片路径
 * @param string $folder 上传目录
 * @since 1.0 2014-12-29 by sutroon
 */
function sofn_set_image($url, $folder = '') {
    echo $url ? "<a href='$url' title='$url' target='_blank'><img src='$url' alt='$url' /></a>" : $url;
}

/**
 * 检测用户是否登录
 * @return mixed false-未登录，array-当前登录用户信息
 * @author 麦当苗儿 <zuojiazi@vip.qq.com>
 */
function sofn_get_login_data($sesnId) {
    $user = session($sid);
    if (empty($user)) {
        return FALSE;
    } else {
        return session('user_auth_sign') == data_auth_sign($user) ? $user : 0;
    }
}

function sofn_get_permission_rules($roleId) {
    
}

/**
 * 更新登录信息
 * @param $data
 * @since 1.0 2014-9-28 by sutroon
 * @example sofn_update_loginData(array('pageSize'=>$pageSize));
 */
function sofn_update_loginData($data) {
    $this->loginData = array_merge($this->loginData, $data);
    session(C('USR_SESSION_ID'), $this->loginData);
}

/**
 * 检测用户权限
 * @param string $key 权限关键词,如 add-user,edit-user,delete-user, 客户管理,新增客户,删除客户等
 * @param bool $end 无权限时是否终止输出,默认true
 * @param array $upr 用户权限数据
 * @return bool
 * @since 1.0 2014-12-29 by sutroon
 * @example 
 *  $this->sofn_check_upr('客户管理');
 *  if (!$this->sofn_check_upr('删除客户', false)) {
 *      $this->act_ajaxReturn(false, '没有权限!');
 *  }
 */
function sofn_check_upr($key, $end = true, $upr = array()) {
    if (!$upr) {
        $upr = $this->UPR;
    }
    if (!$upr || !isset($upr[$key]) || !$upr[$key]) {
        if ($end) {
            die("没有权限!");
        }
        return false;
    }
    return true;
}

/**
 * 检测权限是否存在
 * @param string $code 权限代码
 * @param int $uid 用户编号
 * @return bool
 * @since 1.0 2014-7-24 by sutroon
 * @example sofn_equal_permission('YH_YGGL', $this->loginData['id'])
 */
function sofn_equal_permission($code, $uid) {
    $cache_id = 'userPermissionRule' . $uid;
    $str = S($cache_id);
    if (!$str) {
        $str = M('tuser')->where("id=$uid")->getField('permissionRule');
        S($cache_id, $str, array('expire' => 30));
    }
    if (!$str) {
        return false;
    }
    $ruleID = M('tuser_permission')->where("code='$code'")->getField('id');
    if (!$ruleID) {
        return false;
    }
    if (false === strpos(",$str,", ",$ruleID,")) {
        return false;
    }
    return true;
}

/**
 * 检测网站状态
 * 需在配置文件中配置APP节点
 * @since 1.0 <2015-5-23> SoChishun Added.
 */
function sofn_check_website_status() {
    if (C('APP.WEB_SITE_CLOSE')) {
        exit(C('APP.WEB_SITE_CLOSE_NOTICES'));
    }
}

/**
 * 获取订单状态描述
 * @param string $target, 流程主体,如:xcallcrm
 * @param int $val 状态值
 * @return string
 * @since 1.0 2015-1-9 by sutroon
 * @example sofn_get_order_state_text('xcallcrm',0);
 */
function sofn_get_order_state_text($type, $val) {
    $arr_states = array('0' => '未审核', '1' => '交易完成', '20' => '核单通过', '24' => '核单失败', '30' => '已发货', '未签收', '34' => '仓库退回', '40' => '座席取消', '41' => '买家取消', '42' => '逾期取消', '43' => '订单无效取消', '50' => '客户签收', '54' => '客户拒签', '55' => '客户退货');
    return array_key_exists($val, $arr_states) ? $arr_states[$val] : "未知[$val]";
}

/**
 * 生成序列号(支持10亿以下不重复序号)
 * 规则：[前缀][siteID1|9]0[userID1|9][timestamp14]
 * 生成位数：17-33
 * @param string $prefix 前缀,DD=订单,KH=客户,CZ=充值
 * @param int $siteID
 * @param int $uid
 * @return string
 * @since 1.0 2014-1-9 by sutroon
 */
function sofn_get_serial_no($prefix, $siteID, $uid) {
    return $prefix . $siteID . '0' . $uid . date('Ymdhis');
}

/**
 * 字符串截取，支持中文和其他编码
 * @param string $str 需要转换的字符串
 * @param string $start 开始位置
 * @param string $length 截取长度 默认null
 * @param string $charset 编码格式 默认utf-8
 * @param string $suffix 截断显示字符 默认false
 * @return string
 * @since 1.0 <2015-4-3> SoChishun Refactoring by \Org\Util\String::msubstr
 * @since 1.1 <2015-4-21> SoChishun 新增缩略号自动长度判断逻辑
 */
function sofn_msubstr($str, $start = 0, $length = NULL, $charset = "utf-8", $suffix = false) {
    if (function_exists("mb_substr"))
        $slice = mb_substr($str, $start, is_null($length) ? mb_strlen($str, $charset) - $start : $length, $charset);
    elseif (function_exists('iconv_substr')) {
        $slice = iconv_substr($str, $start, is_null($length) ? iconv_strlen($str, $charset) - $start : $length, $charset);
    } else {
        $re['utf-8'] = "/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xff][\x80-\xbf]{3}/";
        $re['gb2312'] = "/[\x01-\x7f]|[\xb0-\xf7][\xa0-\xfe]/";
        $re['gbk'] = "/[\x01-\x7f]|[\x81-\xfe][\x40-\xfe]/";
        $re['big5'] = "/[\x01-\x7f]|[\x81-\xfe]([\x40-\x7e]|\xa1-\xfe])/";
        preg_match_all($re[$charset], $str, $match);
        $slice = join("", array_slice($match[0], $start, $length));
    }
    if ($suffix && strlen($slice) < strlen($str)) {
        $slice.=true === $suffix ? '...' : $suffix;
    }
    return $slice;
}

/**
 * 截取字符串
 * @param string $str
 * @param int $length
 * @param string $suffix
 * @return string
 * @since 1.0 <2015-4-21> SoChishun Added.
 */
function sofn_subtext($str, $length = 0, $suffix = true) {
    return sofn_msubstr($str, 0, $length, 'utf-8', $suffix);
}

/**
 * 本地相对路径转为绝对路径
 * @param string $path
 * @return string
 * @since 1.0 <2015-4-29> SoChishun Added.
 * @example 
 *  sofn_relative_path_to_absolute('./////Uploads///a/b/c.jpg') => G:\PDC\ProjectEnv\XCall150309\Uploads\a\b\c.jpg
 *  sofn_relative_path_to_absolute('./////Uploads///a/b/c.jpg') => /var/www/html/xcall/Uploads/a/b/c.jpg
 */
function sofn_relative_path_to_absolute($path) {
    $script_name = $_SERVER['SCRIPT_FILENAME'];
    $splitor_srv = false === strpos($script_name, '/') ? '\\' : '/';
    $splitor_path = false === strpos($path, '/') ? '\\' : '/';
    $pos = strrpos($script_name, $splitor_srv);
    if (false !== $pos) {
        $script_name = substr($script_name, 0, $pos);
    }
    if ('.' == $path[0]) {
        $path = substr($path, 1);
    }
    $path = preg_replace("[$splitor_path+]", $splitor_path, $path);
    if ($splitor_path != $splitor_srv) {
        $path = str_replace($splitor_path, $splitor_srv, $path);
    }
    $path = $script_name . $path;
    if ('\\' == $splitor_srv) {
        $path = str_replace($splitor_srv, $splitor_srv . $splitor_srv, $path);
    }
    return $path;
}

/**
 * 二维数组转为HtmlTable
 * @param array $arr 二维数组, 如 array('key1'=>'value1','key2'=>'value2')
 * @param int $cols 列单元数量
 * @param array $config 选项 如 array('TABLE_ATTR'=>'class="strategy-info"')
 * @return string
 * @since 1.0 <2015-5-7> SoChishun Added.
 * @example sofn_array_to_tableform($summary, 3, array('TABLE_ATTR' => 'class="strategy-info"'))
 */
function sofn_array_to_tableform($arr, $cols = 3, $config = array()) {
    if (!$arr) {
        return '';
    }
    $i = 0;
    $config = array_merge(array('TABLE_ATTR' => ''), $config);
    $str = '<table ' . $config['TABLE_ATTR'] . '><tr>';
    foreach ($arr as $th => $td) {
        if ($i > 0 && $i % $cols == 0) {
            $str.='</tr><tr>';
        }
        $str.='<th>' . $th . '</th><td>' . $td . '</td>';
        $i++;
    }
    if ($i > 0 && $i % $cols > 0) {
        $str.='<td colspan="' . (($cols - $i % $cols) * 2) . '"></td>';
    }
    $str.='</tr></table>';
    return $str;
}

/**
 * 加载sql文件为分号分割的数组
 * 支持存储过程和函数提取
 * @param string $path 文件路径
 * @return boolean|array
 * @since 1.0 <2015-5-27> SoChishun Added.
 */
function sofn_load_sql_file($path, $fn_splitor = ';;') {
    if (!file_exists($path)) {
        return false;
    }
    $lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    $arr = false;
    $str = '';
    $skip = false;
    $fn = false;
    foreach ($lines as $line) {
        $line = trim($line);
        // 过滤注释
        if (!$line || 0 === strpos($line, '--') || 0 === strpos('*') || 0 === strpos($line, '/*') || (false !== strpos($line, '*/') && strlen($line) == (strpos($line, '*/') + 2))) {
            if (!$skip && 0 === strpos($line, '/*')) {
                $skip = true;
            }
            if ($skip && false !== strpos($line, '*/') && strlen($line) == (strpos($line, '*/') + 2)) {
                $skip = false;
            }
            continue;
        }
        if ($skip) {
            continue;
        }
        // 提取存储过程和函数
        if (0 === strpos($line, 'DELIMITER ' . $fn_splitor)) {
            $fn = true;
            continue;
        }
        if (0 === strpos($line, 'DELIMITER ;')) {
            $fn = false;
            $arr[] = $str;
            $str = '';
            continue;
        }
        if ($fn) {
            $str.=$line . ' ';
            continue;
        }
        // 提取普通语句
        $str.=$line;
        if (false !== strpos($line, ';') && strlen($line) == (strpos($line, ';') + 1)) {
            $arr[] = $str;
            $str = '';
        }
    }
    return $arr;
}

/**
 * C()方法扩展方法
 * <br />用于获取2层以上的值
 * @param string $name
 * @param mixed $defv
 * @param string $fmt
 * @return string
 * @since 1.0 <2015-7-15> SoChishun Added.
 * @since 1.1 <2015-8-5> SoChishun 新增读取数据库分片配置时,对分片状态的判断
 * @example sofn_get_config('db.DB_SHARDING.DB_IMPORT_TEMP.sharding_db','%s.')
 */
function sofn_get_config($name, $fmt = '', $defv = '') {
    $names = explode('.', $name);
    $n = count($names);
    if ($n > 2) {
        $value = C($names[0] . '.' . $names[1]);
        for ($i = 2; $i < $n; $i++) {
            if (is_null($value) || !is_array($value) || !isset($value[$names[$i]])) {
                return $defv;
            }
            $value = $value[$names[$i]];
        }
    } else {
        $value = C($name);
    }
    if ($value) {
        return $fmt ? sprintf($fmt, $value) : $value;
    }
    return $defv;
}

/**
 * 获取数据库分片配置
 * @param name $name
 * @return boolean|array
 * @since 1.0 <2015-8-20> SoChishun Added.
 * @example sofn_get_sharding_config('DB_CALLNUMBER');
 */
function sofn_get_sharding_config($name) {
    $sharding_config = C('db.DB_SHARDING');
    if (!$sharding_config || !array_key_exists($name, $sharding_config)) {
        return false;
    }
    return $sharding_config[$name];
}

/**
 * 获取数据库分片字段
 * @param string $name 索引
 * @param string $field 字段名称
 * @param string $fmt 格式
 * @param string $defv 默认值
 * @return string
 * @since 1.0 <2015-8-20> SoChishun Added.
 * @example sofn_get_sharding_db('DB_CALLNUMBER')
 */
function sofn_get_sharding_field($name, $field = 'sharding_db', $fmt = '%s.', $defv = '') {
    if (!C('db.DB_SHARDING_STATUS')) {
        return $defv;
    }
    $sharding_config = C('db.DB_SHARDING');
    if (!$sharding_config || !array_key_exists($name, $sharding_config)) {
        return $defv;
    }
    $value = $sharding_config[$name][$field];
    if ($value) {
        return $fmt ? sprintf($fmt, $value) : $value;
    }
    return $defv;
}

/**
 * 输出http cache信息
 * @param type $tmp_time
 * @since 1.0 <2015-6-6> http://www.2cto.com/kf/201202/119285.html
 */
function echo_last_etag($tmp_time) {
    $now_url = get_absolute_url();
    $md5 = md5($now_url . $tmp_time);
    $etag = '"' . $md5 . '"';
    header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $tmp_time) . ' GMT');
    header("ETag: $etag");
    if ((isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $tmp_time) || (isset($_SERVER['HTTP_IF_UNMODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_UNMODIFIED_SINCE']) < $tmp_time) || (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $etag)) {
        header("HTTP/1.1 304 Not Modified");
        exit(0);
    }
}

/**
 * 打开Redis实例
 * @param string $msg
 * @return boolean|\Redis
 * @since 1.0 <2015-8-19> SoChishun Added.
 */
function sofn_new_redis(&$msg = '') {
    $redis = new \Redis();
    if (is_null($redis)) {
        $msg = 'Redis Create Failure!';
        return false;
    }
    try {
        $redis->connect('127.0.0.1', 6379);
        return $redis;
    } catch (Exception $ex) {
        $msg = 'Redis Connect Failure:' . $ex->getMessage();
        return false;
    }
}


/**
 * 应用程序通用函数
 *
 * @since 1.0 2014-6-16 by sutroon
 */
// 录音文件目录 /var/spool/asterisk/monitor/2014/09/21
/**
  ==系统默认强制开放的端口==
  8899:web端口
  8898:录音文件下载端口
  8897:应用语音下载端口
  8896:队列语音下载端口
  5060:sip注册端口
  10000-20000:流媒体端口

  ==可以关闭的端口==
  21:默认ftp端口

  ==白名单验证后开通的端口==
  2389:ssh端口
  3306:mysql远程访问端口

  ==不知道干嘛的端口==
  20
  69
  6600
  4569
 */
/**
 * APP_ROOT_PATH 应用程序目录绝对路径(必须放在分组项目的Common中)
 * @since 1.0 2014-12-31 by sutroon
 */
// define('APP_ROOT_PATH', $_SERVER['DOCUMENT_ROOT'] . __ROOT__ . '/' . APP_NAME . (defined('GROUP_NAME') ? ('/' . C('APP_GROUP_PATH') . '/' . GROUP_NAME) : '') . '/');

/**
 * 上传路径根目录
 */
define('UPLOAD_ROOT', './Uploads/');
define('PUBLIC_PATH', str_replace('./', '/', __ROOT__) . '/Public/');

/**
 * 返回项目的AppData目录<br />包含Conf、Template、TempSqls等
 * @param array $options 选项,包含siteID, userID, type, subdir选项
 * @return string 返回路径地址
 * @since 1.0 2015-1-19 by sutroon
 * @example
 * get_appdata_dir(array('siteID'=>$this->loginData['siteID'],'userID'=>$this->loginData['id'],'type'=>'tempsqls','subdir'=>'CustomerImp')).date('Ymdhis').'.sql'; // 获取sql临时文件目录
 * get_appdata_dir(array('type'=>'template')).'customer_import_file.xlsx'; // 获取模板文件
 * get_appdate_dir(array('type'=>'conf')).'main.conf'; // 获取配置文件
 */
function get_appdata_dir($options) {
    if (!$options || !is_array($options)) {
        return MODULE_PATH . 'AppData/';
    }
    $dirs[] = 'AppData';
    $arrData = array('conf' => 'Conf', 'tempsqls' => 'TempSqls', 'template' => 'Template');
    if (isset($options['type'])) {
        $options['type'] = strtolower($options['type']);
        if (array_key_exists($options['type'], $arrData)) {
            $dirs[] = $arrData[$options['type']];
        }
    }
    if (isset($options['subdir'])) {
        $dirs[] = $options['subdir'];
    }
    $name = '';
    if (isset($options['siteID'])) {
        $name.='S' . $options['siteID'];
    }
    if (isset($options['userID'])) {
        $name.='U' . $options['userID'];
    }
    if ($name) {
        $dirs[] = $name;
    }
    $path = APP_PATH . 'Runtime/' . implode('/', $dirs);
    if (!is_dir($path)) {
        mkdir($path, 0777, true);
    }
    return ($path[strlen($path)] == '/') ? $path : $path . '/';
}

/**
 * 创建AGI_AsteriskManager类实例
 * @return \AGI_AsteriskManager
 * @since 1.0 2014-7-7 by sutroon; 
 * @since 2.0 2014-11-6 by sutroon 增加缓存功能,避免频繁登入登出
 */
function create_asmanager() {
    if (C('astman')) {
        return C('astman');
    }
    vendor('asterisk.phpagi-asmanager');
    $astman = new \AGI_AsteriskManager();
    if (!$astman->connect("127.0.0.1:5038", 'admin', 'amp111', 'OFF')) {
        die('asterisk登录失败!');
    }
    C('astman', $astman);
    return $astman;
}

/**
 * 执行amicommand命令并返回结果
 * @param string $command ami命令
 * @return string
 * @since 2014-10-27 by sutroon
 * @example query_asmanager_result('sip show peers');
 */
function asmanager_query_result($command) {
    $astman = create_asmanager();
    $actionid = NULL;
    $result = $astman->Command($command, $actionid);
    return substr($result['data'], strlen('Privilege: Command'));
}

/**
 * 批量执行ami command命令并返回结果数组
 * @param array $commands
 * @return array
 * @since 1.0 2014-11-6 by sutroon
 * @example asmanager_query_results(array(''));
 */
function asmanager_query_results($commands) {
    $astman = create_asmanager();
    $actionid = NULL;
    foreach ($commands as $name => $command) {
        $result = $astman->Command($command, $actionid);
        $arr_return[$name] = substr($result['data'], strlen('Privilege: Command'));
    }
    return $arr_return;
}

/**
 * 执行ami send_request命令
 * @param array $commands
 * @since 1.0 2014-11-6 by sutroon
 * @example asmanager_send_request(array('moh reload','reload'));
 */
function asmanager_send_request($commands) {
    $astman = create_asmanager();
    $actionid = NULL;
    foreach ($commands as $command) {
        $astman->send_request('Command', array('Command' => $command));
    }
    $astman->disconnect();
}

/**
 * 生成租户ID列
 * @param type $isSuperAdming
 * @param type $siteID
 * @param type $siteIDg
 * @param type $name
 * @return string
 * @since 1.0 2014-7-23 by sutroon
 */
function generate_siteID_li($isSuperAdming, $siteID, $siteIDg, $name) {
    $str_name = '租户ID';
    $str = '';
    $value = $isSuperAdming ? $siteID : $siteIDg;
    if ($isSuperAdming) {
        if ($siteID == '') {
            $str = '<li><label>租户ID：</label><select name="siteID" id="siteID" title="请选择租户" required>';
            $list = M('tuser_member')->where('userType="ADMIN"')->field('siteID, userName')->select();
            foreach ($list as $row) {
                $str .= '<option value="' . $row['siteID'] . '">' . $row['userName'] . '</option>';
            }
            $str .= '</select></li>';
        } else {
            $str = '<li><label>' . $str_name . '：</label>' . ($name == '' ? '<input type="text" name="siteID" id="siteID" value="' . $value . '" readonly="readonly" />' : '<input type="text" value="' . $name . '" readonly="readonly" /><input type="hidden" name="siteID" id="siteID" value="' . $value . '" />') . '</li>';
        }
    } else {
        $str = '<li style="display:none;"><label>' . $str_name . '：</label><input type="hidden" name="siteID" id="siteID" value="' . $value . '" readonly="readonly" /></li>';
    }
    return $str;
}

/**
 * 检查权限代码
 * @param string $code 权限代码
 * @param int $role_id 角色编号
 * @return boolean
 * @since 1.0 <2015-8-4> SoChishun Added.
 * @since 1.1 <2015-8-6> SoChishun 新增$role_id参数
 */
function equal_permission_code($code, $role_id = 0) {
    if (!$code) {
        return false; // 参数无效
    }
    if (!$role_id) {
        $login_data = session(C('USER.SESSION_ID'));
        if (!$login_data || !$login_data['role_id']) {
            return false; // 未登录或角色未分配
        }
        $role_id = $login_data['role_id'];
    }
    $acodes = M('tuser_permission')->cache(true)->getField('code,id');
    if (!$acodes) {
        return false; // 权限数据为空
    }
    if (!array_key_exists($code, $acodes)) {
        return false; // 权限代码不存在
    }
    $srule = M('tuser_role')->where(array('id' => $role_id))->getField('permission_rule');
    if (!$srule) {
        return false; // 角色不存在或角色权限未分配
    }
    if (false === strpos(",$srule,", ',' . $acodes[$code] . ',')) {
        return false; // 没有分配该权限
    }
    return true;
}

/**
 * 检查权限名称
 * @param string $name 权限名称
 * @param int $role_id 角色编号
 * @return boolean
 * @since 1.0 <2015-8-4> SoChishun Added.
 * @since 1.1 <2015-8-6> SoChishun 新增$role_id参数
 */
function equal_permission_name($name, $role_id = 0) {
    if (!$name) {
        return false; // 参数无效
    }
    if (!$role_id) {
        $login_data = session(C('USER.SESSION_ID'));
        if (!$login_data || !$login_data['role_id']) {
            return false; // 未登录或角色未分配
        }
        $role_id = $login_data['role_id'];
    }
    $anames = M('tuser_permission')->cache(true)->getField('name,id');
    if (!$anames) {
        return false; // 权限数据为空
    }
    if (!array_key_exists($name, $anames)) {
        return false; // 权限代码不存在
    }
    $srule = M('tuser_role')->where(array('id' => $role_id))->getField('permission_rule');
    if (!$srule) {
        return false; // 角色不存在或角色权限未分配
    }
    if (false === strpos(",$srule,", ',' . $anames[$name] . ',')) {
        return false; // 没有分配该权限
    }
    return true;
}

/**
 * [已弃用 2015-8-4,推荐使用equal_permission_name]检测权限是否存在
 * @param type $name
 * @param type $username
 * @return type
 * @since 1.0 2014-7-24 by sutroon
 */
function equal_permission($name, $username) {
    $M = M();
    $result = $M->cache('permissionRule' . $username, 30)->query("select permissionRule from tuser_member where userName = '$username'");
    if (!$result || !$result[0]['permissionRule']) {
        return false;
    }
    $rules = $result[0]['permissionRule'];
    $result = $M->query("select id from tuser_permission where name = '$name'");
    if (!$result || !$result[0]['id']) {
        return false;
    }
    $id = $result[0]['id'];
    return strpos(',' . $rules . ',', ',' . $id . ',') !== false;
}

/**
 * ===========================================xcall专用函数=========================================
 */

/**
 * 语音管理-获取语音路径
 * @param string $tag
 * @return type
 */
function get_voc_url($tag) {
    switch ($tag) {
        case 'moh':
            return 'http://' . $_SERVER['SERVER_NAME'] . ':8897';
        case 'sounds':
            return 'http://' . $_SERVER['SERVER_NAME'] . ':8896';
        case 'rec':
            return 'http://' . $_SERVER['SERVER_NAME'] . ':8898/';
    }
    return '';
}

/**
 * 处理录音文件地址
 * @param string $file
 * @return string
 * @since 1.0 2014-7-24 by sutroon
 */
function parse_voc_path($file) {
    // q-2000-18679753678-20140814-223101-1408026661.3643.WAV
    // /monitor/2014/08/10/
    if (strpos($file, '-') === false) {
        return $file;
    }
    $arr = explode('-', $file);
    $path = $arr[3];
    return substr($path, 0, 4) . '/' . substr($path, 4, 2) . '/' . substr($path, 6) . '/' . $file;
}

/**
 * 队列管理-溢出标识
 * @param string $name
 * @return string
 */
function get_overflowStrategy_name($v) {
    switch ($v) {
        case '0':
            return '不溢出';
        case 'queue':
            return '溢出队列';
        case 'extension':
            return '溢出分机';
    }
    return '未知' . $v;
}

/**
 * 获取运营商名称
 * @param int $v
 * @return string
 * @since 1.0 2014-7-1 by sutroon
 */
function get_operator_text($v) {
    switch ($v) {
        case '1':
            return '移动';
        case '2':
            return '联通';
        case '3':
            return '电信';
    }
    return '未知' . $v;
}

/**
 * 获取任务状态名称
 * @param int $v
 * @return string
 * @since 1.0 2014-7-1 by sutroon
 */
function get_ctitask_text($v) {
    switch ($v) {
        case '0':
            return '停止';
        case '1':
            return '启动';
        case '2':
            return '暂停';
    }
    return '未知' . $v;
}

/**
 * 获取中继注册模式名称
 * @param string $v
 * @return string
 * @since 1.0 2014-7-2 by sutroon
 */
function get_regMode_text($v) {
    switch ($v) {
        case 'register':
            return '注册模式';
        case 'ip':
            return 'IP模式';
        case 'ims':
            return 'IMS模式';
    }
    return '未知' . $v;
}

/**
 * 获取用户激活状态名称
 * @param string $v
 * @return string
 * @since 1.0 2014-7-2 by sutroon
 */
function get_activateState_text($v) {
    switch ($v) {
        case '1':
            return '已激活';
        case '0':
            return '未激活';
    }
    return '未知' . $v;
}

/**
 * 获取是否班长模式的名称
 * @param string $v
 * @return string
 * @since 1.0 2014-7-2 by sutroon
 */
function get_isLeader_text($v) {
    switch ($v) {
        case 'Leader':
            return '班长';
        case 'SEAT':
            return '坐席';
        case '1':
            return '一线';
        case '2':
            return '二线';
        case '3':
            return '三线';
    }
    return '未知' . $v;
}

/**
 * 获取通道状态名称
 * @param int $v
 * @return string
 * @since 1.0 2014-7-2 by sutroon
 */
function get_channelState_text($v) {
    switch ($v) {
        case '0':
            return '空闲';
        case '1':
            return '正在发起呼叫';
        case '2':
            return '呼叫失败';
        case '3':
            return '呼叫成功';
    }
    return '未知' . $v;
}

/**
 * 获取通道状态名称
 * @param int $v
 * @return string
 * @since 1.0 2014-7-2 by sutroon
 * 1=接入(策略外呼后转分机)[呼入费率组],2=呼出(策略外呼)[呼出费率组],3=网内(分机互打),4=来话(系统被叫来话),5=外呼(分机直接呼出)[呼出费率组],6=签入(分机签入队列),7=签出(分机签出队列)
 */
function get_channelDirection_text($v) {
    switch ($v) {
        case '1':
            return '接入';
        case '2':
            return '呼出';
        case '3':
            return '网内';
        case '4':
            return '来话';
        case '5':
            return '外呼';
        case '6':
            return '签入';
        case '7':
            return '签出';
        case '8':
            return '未接';
    }
    return '未知' . $v;
}

/**
 * username=$username,trunk=$trunk,agent=$agent,expires_time=$expires_time,ip=$ip
 * @return array
 * @since 1.0 2014-10-15 by sutroon
 */
function get_licence_data() {
    $ip = $_SERVER['SERVER_NAME'];
    $arrDefault = array('username' => '', 'trunk' => 5, 'agent' => 5, 'expires_time' => date('Y-m-d H:i:s', strtotime('1 month')), 'ip' => $ip, 'status' => 0);
    $content = file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/xcallsqzs/Licence/licence.txt');
    if (strlen($content) < 5 || substr($content, 0, 2) != '??') {
        update_cet_data($arrDefault);
        return $arrDefault;
    }
    $content = substr($content, 2);
    $content = str_replace('??', '==', $content);
    $enc=new \Org\Su\SuEncrypt();
    $content = $enc->encrypt_rnd($content, 'DENCRYPT', C('user.ENCRYPT_KEY'));
    $arr = explode(',', $content);
    if (count($arr) > 3) {
        foreach ($arr as $item) {
            $arr1 = explode('=', $item);
            if (count($arr1) > 1) {
                $data[trim($arr1[0])] = trim($arr1[1]);
            }
        }
        if ($data['ip'] == $ip && $data['expires_time'] > date('Y-m-d H:i:s')) {
            $data['status'] = 1;
            update_cet_data($data);
            return $data;
        }
        if ($data['expires_time'] > date('Y-m-d H:i:s')) {
            $arrDefault['expires_time'] = $data['expires_time'];
        }
    }
    update_cet_data($arrDefault);
    return $arrDefault;
}

// 2014-10-15 by sutroon
function update_cet_data($data) {
    return; // 中心服务器挂了，影响登录！！2015-4-20 SoChishun Modified.
    $url = 'http://' . C('USR_CET_HOST') . ':8899/xcallsqzs/update_cet.php';
    file_get_contents_post($url, $data);
}

/**
 * 获取通用是否名称
 * @param int $v
 * @return string
 * @since 1.0 2014-7-2 by sutroon
 */
function get_yesnoState_text($v) {
    switch ($v) {
        case '1':
            return '是';
        case '0':
            return '否';
    }
    return '未知' . $v;
}

/**
 * 获取任务来源的名称
 * @param string $v
 * @return string
 * @since 1.0 2014-7-2 by sutroon
 */
function get_taskSource_text($v) {
    switch ($v) {
        case 'add':
            return '添加号码';
        case 'import':
            return '导入号码';
        case 'generate':
            return '生成号码';
    }
    return '未知' . $v;
}

/**
 * 获取语音目录的名称
 * @param string $v
 * @return string
 * @since 1.0 2014-7-4 by sutroon
 */
function get_vocdir_text($v) {
    switch ($v) {
        case 'moh':
            return '队列等待音';
        case 'sounds':
            return '应用语音';
    }
    return '未知' . $v;
}

/**
 * 获取外呼模式速度模式的名称
 * @param string $v
 * @return string
 * @since 1.0 2014-7-4 by sutroon
 */
function get_speedMode_text($v) {
    switch ($v) {
        case 'channel':
            return '通道数量';
        case 'percent':
            return '座席比例';
    }
    return '未知' . $v;
}

/**
 * 获取外呼模式呼通后操作的名称
 * @param int $v
 * @return string
 * @since 1.0 2014-7-4 by sutroon
 */
function get_outOper_text($v) {
    switch ($v) {
        case '0':
            return '直接转队列';
        case '1':
            return '按码转队列';
        case '2':
            return '按码转菜单';
        case '3':
            return '放音后挂机';
        case '4':
            return '留言后挂机';
        case '5':
            return '按码后挂机';
        case '6':
            return '放音转队列';
        case '7':
            return '直接转队列评价';
        case '8':
            return '按码转队列评价';
        case '9':
            return '放音转队列评价';
        case '10':
            return '应答后外呼';
    }
    return '未知' . $v;
}

/**
 * 获取日期类型的名称
 * @param int $v
 * @return string
 * @since 1.0 2014-7-15 by sutroon
 */
function get_datetype_text($v) {
    switch ($v) {
        case '0':
            return '非工作日';
        case '1':
            return '工作日';
        case '2':
            return '所有';
    }
    return '未知' . $v;
}

/**
 * 获取启用停用的名称
 * @param int $v
 * @return string
 * @since 1.0 2014-7-20 by sutroon
 */
function get_startstop_text($v) {
    switch ($v) {
        case '0':
            return '停用';
        case '1':
            return '启用';
    }
    return '未知' . $v;
}

/**
 * 获取签入类型的名称
 * @param int $v
 * @return string
 * @since 1.0 2014-7-15 by sutroon
 */
function get_sign_text($v) {
    switch ($v) {
        case '0':
            return '登出';
        case '1':
            return '登入';
    }
    return '未知' . $v;
}

function replace_ln($replace, $str) {
    return str_replace("\n", $replace, $str);
}

/**
 * ============================================以下是XCALL专属配置=====================================================
 */
define('app_sub_dir', '/xcall'); // 应用程序子目录
define('trixbox_reset_uri', '/maint/modules/configedit/phpconfig.php?reset=reset'); // trixbox的reset的访问地址
define('trixbox_reload_uri', '');
define('sip_additional_path', '/etc/asterisk/sip_additional.conf'); //分机设置路径
define('queues_additional_path', '/etc/asterisk/queues_additional.conf'); //队列设置路径
define('sip_registrations_path', '/etc/asterisk/sip_registrations.conf'); //中继设置路径
define('musiconhold_additional_path', '/etc/asterisk/musiconhold_additional.conf'); //语音文件搜索目录设置路径
// 文件内容模板
define('sip_additional_tpl', "\n\n[{user_extension}]\ndeny=0.0.0.0/0.0.0.0\ntype=friend\nsecret={secret}\nqualify=yes\nport=" . C('CTI_PORT') . "\npickupgroup=\npermit=0.0.0.0/0.0.0.0\nnat=yes\nmailbox={mailbox}@device\nhost=dynamic\ndtmfmode=rfc2833\ndial=SIP/{user_extension}\ncontext={context}\ncanreinvite={canreinvite}\ncallgroup=\ncallerid={user_extension} <{user_extension}>\ncall-limit=1\ntrustrpid=yes\nsendrpid=pai\nqualifyfreq=60\ntransport=udp\nencryption=no\ncc_monitor_policy=generic\ncallcounter=yes\nfaxdetect=no");
// define('sip_additional_tpl', "\n\n[{user_extension}]\ndeny=0.0.0.0/0.0.0.0\ntype=friend\nsecret={secret}\nqualify=yes\nport=".C('CTI_PORT')."\npickupgroup=\npermit=0.0.0.0/0.0.0.0\nnat=yes\nmailbox={mailbox}@device\nhost=dynamic\ndtmfmode=rfc2833\ndial=SIP/{user_extension}\ncontext={context}\ncanreinvite={canreinvite}\ncallgroup=\ncallerid={user_extension} <{user_extension}>\naccountcode=\ncall-limit=1\ntrustrpid=yes\nsendrpid=pai\nqualifyfreq=60\ntransport=udp\nencryption=no\navpf=no\nicesupport=no\ncc_monitor_policy=generic\ncallcounter=yes\nfaxdetect=no");
define('queues_additional_tpl_bak', "\n\n[{queueno}]\nannounce-frequency=0\nannounce-holdtime=no\nannounce-position=no\nautofill=no\neventmemberstatus=no\neventwhencalled=no\njoinempty=yes\nleavewhenempty=no\nmaxlen=0\nmusic={music}\nperiodic-announce-frequency=0\nqueue-callswaiting=silence/1\nqueue-thereare=silence/1\nqueue-youarenext=silence/1\nretry=5\nstrategy={strategy}\ntimeout=15\nweight=0\nwrapuptime=0");
define('sip_additional_reg_tpl', "\n\n[{trunk}]\ndisallow={disallow}\ntype={type}\nnat={nat}\naccountcode={trunk}\nhost={host}\nport={port}\nfromdomain={fromdomain}\nusername={username}\nsecret={secret}\nfromuser={fromuser}\ncontext={context}\ninsecure={insecure}\ndtmfmode={dtmfmode}\n{allow}"); // 外线ip模式模板
define('sip_additional_ip_tpl', "\n\n[{trunk}]\ndisallow={disallow}\ntype={type}\nnat={nat}\naccountcode={trunk}\nhost={host}\nport={port}\ninsecure=port,invite\ncontext={context}\ndtmfmode={dtmfmode}\n{allow}"); // 外线ip模式模板
define('sip_trunk_reg_tpl', "\n\nregister={username}:{secret}@{host}:{port}"); // 外线注册模式模版
define('musiconhold_additional_tpl', "\n\n[{dirname}]\nmode=files\ndirectory={path}"); //语音目录模板
define('queues_additional_tpl', "\n\n[{queueno}]\nannounce-frequency={announce-frequency}\nannounce-holdtime={announce-holdtime}\njoinempty={joinempty}\neventmemberstatus={eventmemberstatus}\neventwhencalled={eventwhencalled}\nautofill={autofill}\nleavewhenempty={leavewhenempty}\nwrapuptime={wrapuptime}\nannounce-position={announce-position}\nmusic={music}\nperiodic-announce-frequency={periodic-announce-frequency}\nqueue-callswaiting={queue-callswaiting}\nqueue-thereare={queue-thereare}\nqueue-youarenext={queue-youarenext}\nretry={retry}\nstrategy={strategy}\ntimeout={timeout}\nweight={weight}\nmaxlen={maxlen}\nperiodic-announce={over_music}\nperiodic-announce-frequency=6");
//常用枚举类型
//队列振铃模式
define('queues_additional_strategy', serialize(array('rrmemory', 'random', 'fewestcalls', 'leastrecent', 'roundrobin', 'ringall')));

/**
 * 将原文加密成符合xcall数据库存储要求的密文
 *
 * @param string $str
 * @return string
 * @since 1.0 2014-6-17 by sutroon
 * @example
 * sofunc_password(admin123) // DB94E062
 * sofunc_password(123456) // 3FEC1D87
 * sofunc_password(123) // ED842E65
 * sofunc_password(1) // DF616A35
 */
function sofunc_password($str) {
    return \Org\Su\Encrypt::password($str);
}

/**
 * 获取规范的Url参数sort
 * <br />如: $sortby = sofunc_get_orderby(I('sort'))
 * @param string $sort
 * @return boolean
 * @since 1.0 2015-2-2 by sutroon
 */
function sofunc_get_orderby($sort) {
    if ($sort) {
        switch ($sort[0]) {
            case 'D':
                return substr($sort, 1) . ' DESC';
            case 'A':
                return substr($sort, 1);
            default:
                return $sort;
        }
    }
    return false;
}

// 设置费率组名称
function sofn_set_payfee_text($list) {
    if (!$list || false === $col) {
        return $list;
    }
    $names = D('Payfee')->scope_fields();
    foreach ($list as $row) {
        $outFeeRate = $row['outFeeRate'];
        $inFeeRate = $row['inFeeRate'];
        $smsFeeRate = $row['smsFeeRate'];

        $row['outFeeRate'] = $names[$outFeeRate];
        $row['inFeeRate'] = $names[$inFeeRate];
        $row['smsFeeRate'] = $names[$smsFeeRate];
        $arr[] = $row;
    }
    return $arr;
}

/**
 * 检测用户余额状态,欠费返回False，否则返回True
 * @param $siteID
 * @return bool
 * @since 1.0 2014-9-24 by sutroon
 */
function sofn_check_user_blance_state($siteID) {
    $list = M('tuser_member')->where("userType='ADMIN' and siteID=$siteID")->getField('siteID,blance,allowOverdraftAmount,payType');
    if (!$list) {
        return false;
    }
    $blance = $list[$siteID]['blance'];
    $allowOverdraftAmount = $list[$siteID]['allowOverdraftAmount'];
    $payType = $list[$siteID]['payType'];
    if ($payType == '1' && $blance < 0 && abs($blance) >= $allowOverdraftAmount) {
        return false;
    }
    return true;
}

/**
 * 获取租户控件
 * @param type $isSuperAdmin
 * @param type $siteID
 * @return string
 * @since 1.0 2014-10-28 by sutroon
 */
function sofn_get_siteID_control($isSuperAdmin, $siteID) {
    if ($isSuperAdmin) {
        $str = '<select name="siteID" title="请选择租户" required>';
        $list = sofn_get_users();
        foreach ($list as $k => $v) {
            $str .= '<option value="' . $k . '">' . $v . '</option>';
        }
        return $str . '</select>';
    } else {
        return '<input type="hidden" name="siteID" value="' . $siteID . '" />';
    }
}

/**
 * 设置数据集列表的siteID值
 * @param $list
 * @since 1.0 2014-10-25 by sutroon
 */
function sofn_set_site_id_text($list, $col = null) {
    if (!$list || false === $col) {
        return $list;
    }
    $users = sofn_get_users(array());
    if (is_null($col)) {
        $col = 'siteID';
    }
    foreach ($list as $row) {
        $siteID = $row[$col];
        $row[$col] = isset($users[$siteID]) ? $users[$siteID] : $siteID;
        $arr[] = $row;
    }
    return $arr;
}

/*
 * 获取siteID名称
 * @since 1.0 <2015-7-2> SoChishun Added.
 */

function get_siteID_text($site_id) {
    if (!$site_id) {
        return $site_id;
    }
    $admins = AddonD('Admin')->scope_fields();
    return isset($admins[$site_id]) ? $admins[$site_id] : $site_id;
}

/**
 * 获取租户列表
 * 格式：array(siteID=>userName[siteID])
 * @param array $prepend
 * @return array
 * @since 1.0 2014-10-21 by sutroon
 * @since 2.0 <2015-4-23> SoChishun Refacting.
 */
function sofn_get_users($prepend = array('' => '选择租户')) {
    $list = AddonD('Admin')->scope_fields();
    if ($list && $prepend) {
        $list = $prepend + $list;
    }
    return $list;
}

/**
 * 获取座席列表
 * @param int $siteID
 * @param array $prepend
 * @return array
 * @since 1.0 2014-12-29 by sutroon
 */
function sofn_get_seats($siteID, $prepend = array('' => '选择工号')) {
    $list = S('seat_id_names' . $siteID);
    if (!$list) {
        $list = M('tuser_member')->where("siteID=$siteID and userType='SEAT'")->getFields(array('userName as id', "userName"));
        S('seat_id_names' . $siteID, $list, array('expire' => 15));
    }
    if ($list && $prepend) {
        $list = $prepend + $list;
    }
    return $list;
}

/**
 * 格式化路径中的分隔符号
 * @param string $path
 * @return string
 * @since 1.0 2015-1-20 by sutroon
 */
function sofunc_dir_format($path) {
    if (PHP_EOL == "\r\n") {
        $path = str_replace('\\', '\\\\', $path);
        $path = str_replace('/', '\\\\', $path);
    } else {
        $path = str_replace('//', '/', $path);
    }
    return $path;
}

/**
 * 获取树形数据表
 * @param string $table 数据表名称
 * @param string $where 查询条件,占位符{0},如: parentID={0} and name like '%abc%'
 * @param int $parentID 父级编号,默认是0
 * @param array $options 可选参数, 包含: string:FIELDS, string:ORDERBY
 * @return array 返回包含sub子数组的数组
 * @since 1.0 2014-10-28 by sutroon
 * @example sofunc_get_tree_table('tuser_permission', "parentID={0}", 0, array('ORDERBY' => 'id asc'));
 */
function sofunc_get_tree_table($table, $where = 'parentID={0}', $parentID = 0, $options = array()) {
    $opts = array('FIELDS' => '*', 'ORDERBY' => 'id');
    if ($options) {
        $opts = array_merge($opts, $options);
    }
    $orderby = $options['ORDERBY'] ? ' order by ' . $opts['ORDERBY'] : '';
    $search = is_array($where) ? sofunc_convert_array_search_to_string($where) : $where;
    if ($search) {
        $search = ' where ' . str_replace('{0}', $parentID, $search);
    }
    $list = M()->query('select ' . $opts['FIELDS'] . " from $table $search $orderby");
    /*
      if (is_array($where)) {
      if (isset($where['parentID'])) {
      $where['parentID'] = str_replace('{0}', $parentID, $where['parentID']);
      }
      } else {
      $where = str_replace('{0}', $parentID, $where);
      }
      $M = M($table)->where($where)->field($opts['FIELDS']);
      if ($opts['ORDERBY']) {
      $M->order($opts['ORDERBY']);
      }
      $list = $M->select();
      echo 'select '.$opts['FIELDS'].' from '.$table.' where '.$search;
     */
    $arr = array();
    if ($list) {
        foreach ($list as $row) {
            $row['sub'] = sofunc_get_tree_table($table, $where, $row['id'], $options);
            $arr[] = $row;
        }
    }
    return $arr;
}
